package com.itheima.health.security;

import com.alibaba.fastjson.JSON;
import com.itheima.health.entity.Result;
import com.itheima.health.pojo.Permission;
import com.itheima.health.pojo.Role;
import com.itheima.health.pojo.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author 程达
 * @PackageName:com.itheima.health.security
 * @ClassName:HealthSecurityFilter
 * @Description:
 * @date 2022/6/9 19:10
 **/
@Slf4j
// 6.6 过滤器拦截所有的后端请求
@WebFilter(urlPatterns = "/*")
//注意： 想要实现资源的权限控制需要使用过滤器来控制（注意授权,资源权限,两个内容）
// 6.4 过滤器拦截所有的后端请求  进行资源的权限过滤 (重写他的3个方法)
public class HealthSecurityFilter implements Filter {

    // 一、无需登录的进行判断放行  6.8 定义不需要登录，就可以访问的URI  定义无需登录就放行的数组
    private String[] unLoginAccessUrlList = new String[]{
            "/user/login",
            "/user/logout",
            "/common/**",
            "/mobile/**",// 移动端的无需登录都可以访问接口
    };

    //6.5  二、定义登录后，需要授权访问的URI，（没在此map的，登录即可访问） 定义需要正确权限能访问到的资源map集合
    private Map<String, String> loginAuthUrlMap = new HashMap<>();
    //参数说明： uri   权限校验码 用map思想来存
    // 后端需要权限校验的功能  比如 检查组的删除  /checkgroup/delete
    // 权限校验码 需要查看用户是否具备   CHECKGROUP_DELETE

    //6.6  二.1 模拟读取  哪些路径   需要   哪些权限可以访问   的列表   添加需要正确权限能访问到的资源map集合数据
    private void initAuthUrlMap() {
        // 未在列表中的，默认用户登录就可以访问
        // 在列表中的，需要判断当前用户是否有匹配的权限，才可以访问
        // 1.比如以下URI，需要用户有CHECKITEM_DELETE权限
        loginAuthUrlMap.put("/checkitem/delete", "CHECKITEM_DELETE");
        loginAuthUrlMap.put("/checkgroup/delete", "CHECKGROUP_DELETE");
        loginAuthUrlMap.put("/setmeal/delete", "SETMEAL_DELETE");
        // 2.比如访问report下的所有内容，需要用户有REPORT_VIEW权限，才可以访问
        loginAuthUrlMap.put("/report/**", "REPORT_VIEW");
    }


    //注意：上面都是声明各种路径，下面开始进行判断
    // 三.1、判断是否不需要登录，可以访问的资源
    private boolean checkURI(String reqUri) {
        // 2.这个类，可以匹配通配符
        AntPathMatcher pathMatcher = new AntPathMatcher();
        // 3.这里面处理逻辑
        // 4.遍历数组，让数组的每个元素与当前的uri比对
        // 5.backend/index.html
        for (String url : unLoginAccessUrlList) {
            if (pathMatcher.match(url, reqUri)) {//6.url是里面的规则，后面的是当前请求的路径，判断是不是这里面的定义的路径数据
                log.info("[权限检查过滤器],匹配放行的url:{}", url);
                return true;
            }
        }
        return false;
    }

    // 四、判断当前的资源， 是否需要登录授权(2个参数：当前请求路径和当前用户)
    private boolean checkLoginAuthURI(String reqUri, User user) {
        // 0. 这个类，可以匹配通配符
        AntPathMatcher pathMatcher = new AntPathMatcher();
        // 基本思路：
        // 1. 先匹配Key，再通过key，获取这个路径需要的权限,如果没有匹配，默认可以直接访问
        // 2. 获取用户的权限列表，判断是否与当前的资源匹配，匹配即有授权，不匹配，即未授权
        //3.先获取所有需要权限的路径
        Set<String> keySets = loginAuthUrlMap.keySet();
        String currKeyUrl = null;
        for (String urLKey : keySets) {
            //4.遍历处每个路径，跟当前的访问路径进行比较，看是不是在里面，在就继续判断，不在就直接访问，
            // 这里把这个当前访问路径用个成员变量进行接收，再进行后面的判断
            if (pathMatcher.match(urLKey, reqUri)) {
                currKeyUrl = urLKey;
            }
        }
        //4.如果是null,那就直接访问
        if (currKeyUrl == null) {
            // 没有匹配，即登录后，无需授权访问，可以直接访问
            log.info("[权限检查过滤器],当前用户访问的URI，无需权限，直接访问，reqUri：{}", reqUri);
            return true;
        }
        //5.如果不是null那就是在这个权限路径里面 继续判断 拿到这个需要访问这个路径的权限字段
        String authKeyword = loginAuthUrlMap.get(currKeyUrl);
        log.info("[权限检查过滤器],当前用户访问的uri需要授权，currKeyUrl:{},keyWord:{}", currKeyUrl, authKeyword);
        log.info("[权限检查过滤器],当前用户信息：user:{}", user);

        // 判断当前用户是否拥有此权限
        // 查看用户都有那些角色   角色都有哪些权限  对应角色集合
        // 遍历该用户的所有权限   跟  能通过的权限关键字 进行比较  ok 就具备资源访问权限
        //6.获取用户的角色集合（角色集合里面有权限字段）
        Set<Role> roles = user.getRoles();
        for (Role role : roles) {
            //7.获取每个角色的权限集合
            Set<Permission> permissions = role.getPermissions();
            //8.遍历每个权限
            for (Permission p : permissions) {
                //9.用当前请求路径需要的权限和这里面的去比，要是有就放行
                if (p.getKeyword().equals(authKeyword)) {//比如这个：SETMEAL_DELETE
                    log.info("[权限检查过滤器],当前用户匹配权限，允许方式，通过");
                    return true;
                }
            }
        }
        log.info("[权限检查过滤器],当前用户匹配权限不匹配，未通过");
        return false;
    }

    //五、处理拦截和放行的方法
    @Override
    public void doFilter(ServletRequest request1, ServletResponse response1, FilterChain filterChain) throws IOException, ServletException {
        //1.拦截所有请求
        HttpServletRequest request = (HttpServletRequest) request1;
        HttpServletResponse response = (HttpServletResponse) response1;
        //2 放行处理
        // 2.1 获取URI
        String uri = request.getRequestURI();
        log.info("[权限检查过滤器...],当前uri:{}", uri);
        // 2.2.   @1   判断URI是否可以无需登录，即可访问
        boolean isNoLoginAccess = checkURI(uri);

        if (isNoLoginAccess) {
            // 2.3.不需要登录，直接放行
            log.info("[权限检查过滤器],允许放行");
            filterChain.doFilter(request, response);
            return;
        }

        // 2.4.@2 如果要登录，判断是否登录过
        if (request.getSession().getAttribute("user") != null) {
            User user = (User) request.getSession().getAttribute("user");
            log.info("[权限检查过滤器],已登录，检查当前用户是否需要授权访问，用户信息:{}", user.getUsername());

            // 2.5  @3 检查当前用户是否对Uri，是否需要授权访问权限

            boolean isLoginAuth = checkLoginAuthURI(uri, user);
            if (isLoginAuth) {
                filterChain.doFilter(request, response);
            } else {
                // 已登录过，@4 但是访问的资源没有权限，前端处理错误信息显示
                response.setContentType("application/json; charset=UTF-8");
                response.getWriter().write(JSON.toJSONString(new Result(false, "无操作权限")));
            }
        } else if (request.getSession().getAttribute("member") != null) {
            log.info("[权限检查过滤器],移动端用户登录，允许放行");
            filterChain.doFilter(request, response);
        } else {
            // 2.6. @5  如果未登录，返回未登录的错误信息，前端处理错误信息显示
            response.setContentType("application/json; charset=UTF-8");
            response.getWriter().write(JSON.toJSONString(new Result(false, "请重新登录")));
        }
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化的时候调用这个方法读取资源需要的权限
        initAuthUrlMap(); // 权限列表
        Filter.super.init(filterConfig);
    }


    @Override
    public void destroy() {
        Filter.super.destroy();
    }

}
